package checker

import (
	"kumachan/standalone/rpc"
	"kumachan/standalone/rpc/kmd"
	"kumachan/interpreter/lang/common/name"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/checker/typsys"
)

func makeServiceIndex(types ([] TypeDef), t_reg TypeRegistry, tim KmdTypeInfoMapping, mic ModuleInfoCollection) (rpc.ServiceIndex, ServiceInfoMapping, source.Errors) {
	var idx = make(rpc.ServiceIndex)
	var sim = make(ServiceInfoMapping)
	var err = (func() source.Errors {
	var errs source.Errors
	for _, def := range types {
		var mi = mic.GetTypeModuleInfo(def)
		var manifest = &(mi.ModManifest)
		var meta = &(def.Metadata.Service)
		var id = rpc.MakeServiceIdentifier (
			manifest.Vendor,
			manifest.Project,
			meta.Name,
			meta.Version,
		)
		var err = (func() *source.Error {
		// 1. check duplicate
		var _, duplicate = idx[id]
		if duplicate {
			return source.MakeError(def.Location,
				E_RpcDuplicateServiceIdentifier {
					ServiceIdentifier: id.String(),
				})
		}
		// 2. check interface
		var interface_, is_interface = def.Content.(*typsys.Interface)
		if !(is_interface) {
			return source.MakeError(def.Location,
				E_RpcServiceNotInterface {})
		}
		// 3. check parameters
		if len(def.Parameters) > 0 {
			return source.MakeError(def.Location,
				E_RpcGenericService {})
		}
		// 4. check included
		if len(def.Implements) > 0 {
			return source.MakeError(def.Location,
				E_RpcServiceWithBaseInterface {})
		}
		// 5. convert methods
		var m, m_index, err1 = serviceConvertMethods(interface_.Methods, tim)
		if err1 != nil { return err1 }
		// 6. find constructor argument type
		var ca, ca_kmd, err2 = serviceFindCtorArgType(def, t_reg, tim)
		if err2 != nil { return err2 }
		var service = rpc.ServiceInterface {
			Identifier:  id,
			Constructor: rpc.ServiceConstructorInterface {
				ArgType: ca_kmd,
			},
			Methods: m,
		}
		idx[id] = service
		sim[def.TypeDef] = ServiceInfo {
			Identifier:  id,
			CtorArgType: ca,
			MethodIndex: m_index,
		}
		return nil
		})()
		source.ErrorsJoin(&errs, err)
	}
	return errs
	})()
	if err != nil { return nil, nil, err }
	return idx, sim, nil
}

func serviceConvertMethods(methods typsys.Record, tim KmdTypeInfoMapping) ((map[string] rpc.ServiceMethodInterface), (map[string] uint), *source.Error) {
	var methods_kmd = make(map[string] rpc.ServiceMethodInterface)
	for _, field := range methods.Fields {
		var loc = field.Attr.Location
		var field_name = field.Name
		var field_t = field.Type
		var in_kmd, out_kmd, out_multi, err = (func() (*kmd.Type, *kmd.Type, bool, *source.Error) {
			var out_multi = false
			var in, out, ok = rpcMethod_(field_t)
			if !(ok) {
				out_multi = true
				in, out, ok = rpcMethodMulti_(field_t)
				if !(ok) {
					return nil, nil, false, source.MakeError(loc,
						E_RpcServiceInvalidMethodType {
							TypeName: typsys.DescribeType(field_t, nil),
						}) } }
			var in_kmd, err = kmdGetType(in, tim, loc)
			if err != nil { return nil, nil, false, err }
			{ var out_kmd, err = kmdGetType(out, tim, loc)
			if err != nil { return nil, nil, false, err }
			return in_kmd, out_kmd, out_multi, nil }
		})()
		if err != nil { return nil, nil, err }
		methods_kmd[field_name] = rpc.ServiceMethodInterface {
			ArgType:    in_kmd,
			RetType:    out_kmd,
			MultiValue: out_multi,
		}
	}
	var methods_index = make(map[string] uint)
	for k, v := range methods.FieldIndexMap {
		methods_index[k] = v
	}
	return methods_kmd, methods_index, nil
}

func serviceFindCtorArgType(def TypeDef, reg TypeRegistry, tim KmdTypeInfoMapping) (*typsys.TypeDef, *kmd.Type, *source.Error) {
	var mod = def.Name.ModuleName
	var item = def.Name.ItemName
	var target_item = (item + T_Suffix_SrvCtorArg)
	var target_name = name.MakeTypeName(mod, target_item)
	var target_def_, exists = reg[target_name]
	var target_def = target_def_.TypeDef
	if exists {
		var k, err = kmdGetType(defType(target_def), tim, target_def.Location)
		if err != nil { return nil, nil, err }
		return target_def, k, nil
	} else {
		return nil, nil, source.MakeError(def.Location,
			E_RpcServiceMissingConstructorArgumentType {
				TypeDefName: def.Name.String(),
			})
	}
}


